// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
// see COPYRIGHT for reuse legalities
//

// EveCommands have two important pieces of state in them: a single, atomic
// eve command that they are based on, and a list of samples, representing
// the various values of that eve command over the course of a scene.

// They conform to the WWAnimatable protocol, and are probably the simplest
// objects that conform to it.


#import "EveCommand.h"
#import "TCLCommand.h"
#import "WWTCLKit.h"
//#import "WWTCLClosedCmd.h"

#import "WWEveParser.h"
#import "Protocol_WWSample.h"
#import "WWSample.h"
#import "WWSampleList.h"
#import "WW3DShape.h"
#import "WW3DWell.h"  // has the sceneClock protocol - formalize this!

@implementation EveCommand

+ initialize { return [EveCommand setVersion:2], self; }

static char *WriteProc(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
   id  eveCmd;


  if ([(id)clientData respondsTo:@selector(datum)])
  {  eveCmd = [(id)clientData datum];
     if ([eveCmd conformsTo:@protocol(WWAnimatable)])
     {  [eveCmd resample];
     }
     else
     {  NXLogError("%s doesn't conform to the WWAnimatable protocol - ", [eveCmd name]);
        NXLogError("why was it stored as the datum of a WWTCLVarTrace and sent to an EveCommand's WriteProc?\n"); 
     }
  }
  else
  {  NXLogError("%s isn't a WWTCLVarTrace - why is it getting sent to an EveCommand's WriteProc?\n", [(id)clientData name]);
  }
  return NULL;
}


- initWithInterp:newInterp eveParser:newEveParser shape:newShape cmd:(char *)newCmd clock:newSceneClock frozen:(BOOL)frozenOrMalleable
{
  char            *ptr, 
                  *firstCmd = NULL, 
                  *cmdString;
  id <WWSample>   newSample;
  WWTCLClosedCmd  *tmpCmd, *closedCmd;


  [super init];
  
  isTCLCommand = NO;
  interp = newInterp; 
  eveParser = newEveParser; 
  myShape = newShape;

  // we'll use this temporarily...
  tmpCmd = [[WWTCLClosedCmd alloc] initWithInterp:interp];
  [tmpCmd setOriginalExpression:newCmd]; 
  cmd = tmpCmd;
  sceneClock = newSceneClock;
  samplesList = [[WWSampleList alloc] initCount:1];  // so we don't need to grow for the default case

  // the following might not just be a simple subclass of RIBCommand,
  // but rather a tcl command or an articulated procedure.  if it's an
  // articulatable tcl command like "set", we need to catch that 

  ptr = cmdString = (char *)[tmpCmd cmd];
  // "set yabba" would give a ptr of 3
  while (*ptr && (*ptr != ' ')) { ptr++; }
  firstCmd = (char *)calloc(1, (1 + (ptr - cmdString)));
  strncpy(firstCmd, cmdString, (ptr - cmdString));

  if (!strcmp(firstCmd, "set"))
  {  // the articulated command is the tcl command "set"
     // we need to malloc up a TCLCommand object and we're done

     // WAVE: Need to deal with sampling tcl variables over time...
     // need to grab this value and save it as a sample, right?

     if (!frozenOrMalleable)
     {  [eveParser evaluateEveCommand:(char *)[cmd cmd]]; // go ahead and do the set now...
        newSample = [[[WWSample alloc] init] setData:[[[TCLCommand alloc] init] setCmd:[tmpCmd cmd]] 
                                             timestamp:[sceneClock timestamp] 
                                             generator:[eveParser currentSampleGeneratorName] 
                                             weight:[eveParser currentSampleWeight]];
        [samplesList addSample:newSample];
     }

     isTCLCommand = YES;
  }
  else
  {  // okay, it's a regular old RIBCommand (I hope...)
     // we need to evaluate the eve command at least once to get initial info from it.
     if (!frozenOrMalleable)
     {  firstSampleData = [self produceSampleFromCmd];
        if (!firstSampleData)
        {  return nil;
        }
        newSample = [[[WWSample alloc] init] setData:firstSampleData 
                                             timestamp:[sceneClock timestamp] 
                                             generator:[eveParser currentSampleGeneratorName] 
                                             weight:[eveParser currentSampleWeight]];
        [samplesList addSample:newSample];
     }
     else
     {  firstSampleData = nil;
     }
  }

  // need to parse cmd to get the names of all the tcl variables referenced - only if malleable
  if (!frozenOrMalleable)
  {  closedCmd = [[eveParser tclInterp] generateClosureFrom:(char *)[cmd cmd] 
                                        andForGlobalsCall:(Tcl_VarTraceProc *)WriteProc 
                                        usingData:(ClientData)self];
     //NXLogError("cmd:\n\t<%s>\nbecame closedCmd:\n\t<%s>\n", [cmd cmd], [closedCmd cmd]);
     cmd = closedCmd;
     [tmpCmd free];  // we're done with this; lose it
  }
  else
  {  // we're gonna hold on to the tmpCmd;
  }


  // since an EveCommand also might be a "set" command, which wouldn't have a firstSampleData, we do this...
  dirtyBoundingBox = YES;
  hasBoundingBox = NO;
  isMotionBlurrable = NO;
  isCompoundCommand = NO;
  if (firstSampleData)
  {  hasBoundingBox = [firstSampleData hasBoundingBox];
     isMotionBlurrable = [firstSampleData isMotionBlurrable];
     isCompoundCommand = [firstSampleData isCompoundCommand];
     pushesCTM = [firstSampleData pushesCTM];
     popsCTM = [firstSampleData popsCTM];
  }

  if (firstCmd)
  {  int  firstCmdLen = strlen(firstCmd);
     sampleName = (char *)malloc(firstCmdLen + 3);
     sampleName[0] = '(';
     strcpy((sampleName + 1), firstCmd);
     sampleName[firstCmdLen + 1] = ')';
     sampleName[firstCmdLen + 2] = '\0';
     free(firstCmd);
  }

  cacheIsFilled = NO;

      
  return self;
}

- copyFromZone:(NXZone *)zone  {  return [super copyFromZone:zone]; }

- (BOOL)isLerpable { return NO; }
- lerpWith:b by:(float)uValue { return self; }
- lerpSelfWith:b by:(float)uValue { return self; }

- (BOOL)pushesCTM { return pushesCTM; }
- (BOOL)popsCTM { return popsCTM; }
- (BOOL)pushesOrPopsCTM { return (popsCTM || pushesCTM); }

- awake
{
  [super awake];

  if (!eveParser)
  {  NXLogError("warning!: the EveCmd <%s> has been disconnected from its eveParser.\n", cmd);
     return nil;
  }
  if (!myShape)
  {  NXLogError("warning!: the EveCmd <%s> has been disconnected from its shape.\n", cmd);
     return nil;
  }

  firstSampleData = [samplesList objectAt:0];
  cacheIsFilled = NO;
  dirtyBoundingBox = YES;

  return self;
}

- free
{
  //NXLogError("%s %p being free'ed - freeing cmd %p, samplesList %p and its objects\n", 
  //	       [[self class] name], self, cmd, samplesList);
  [cmd free]; // this takes care of the traces
  [[samplesList freeObjects] free];

  return [super free];
}

- evalAndAddAsSamples:(char *)newSamplesList
{
   int            argc, argc2, argc3, argc4, i, j;
   char           **argv = NULL,
                  **argv2 = NULL,
                  **argv3 = NULL,
                  **argv4 = NULL;
   id <WWSample>  newSample = nil, 
                  sampleData = nil;
  WWTCLClosedCmd  *theCmd;
  float           currentTimeStamp;


   // we're gonna muck with the cache; don't let it think it's clean   
   cacheIsFilled = NO;

   // we want to hold on to cmd...
   theCmd = cmd;

   Tcl_SplitList([interp interp], newSamplesList, &argc, &argv);  // argc == how many timestamps
   // okay, we've now split it into the list of time samples
   // for each time sample, we want to split the list into component samples
   for (i = 0; i < argc; i++) // for each timestamp
   {  
      Tcl_SplitList([interp interp], argv[i], &argc2, &argv2);  // argc2 == 2
      if (argc2 != 2)
      {  NXLogError("sample #%d of cmd <%s> was badly formed: %d arguments instead of two!\n", 
		    i, [cmd cmd], argc2);
	 if (argv) {  free(argv); }
	 if (argv2) {  free(argv2); }
         cmd = theCmd;
	 return nil;
      }
      // okay, we've now got a time sample
      // argv2[0] == timestamp
      // argv2[1] == list of sample components
      currentTimeStamp = (float)atof(argv2[0]);

      Tcl_SplitList([interp interp], argv2[1], &argc3, &argv3); // argc3 == number of sample components
      for (j = 0; j < argc3; j++)
      {  
        Tcl_SplitList([interp interp], argv3[j], &argc4, &argv4); // argc4 == 3
         if (argc4 != 3)
         {  NXLogError("sample component #%d of sample #%d of cmd <%s> was badly formed: %d arguments instead of three!\n", 
		       j, i, [cmd cmd], argc4);
	    if (argv) {  free(argv); }
	    if (argv2) {  free(argv2); }
	    if (argv3) {  free(argv3); }
	    if (argv4) {  free(argv4); }
            cmd = theCmd;
	    return nil;
	 }
         // okay, we've now got a list of named components that comprise this sample
         // argv4[0] == generatorName
         // argv4[1] == weight
         // argv4[2] == compiled cmd
         cmd = [[WWTCLClosedCmd alloc] initWithInterp:interp];
         [cmd setOriginalExpression:argv4[2]]; 
         sampleData = [self produceSampleFromCmd];
         if (!sampleData)
         {  NXLogError("warning: the animatable expression <%s> was not able to ", [cmd cmd]);
            NXLogError("generate a valid renderable object for itself when rebuilding from a sample list\n");
		       
   	    if (argv) {  free(argv); }
	    if (argv2) {  free(argv2); }
	    if (argv3) {  free(argv3); }
	    if (argv4) {  free(argv4); }
            [cmd free];
            cmd = theCmd;
	    return nil;
         }
         newSample = [[[WWSample alloc] init] setData:sampleData 
                                                 timestamp:currentTimeStamp
                                                 generator:argv4[0] weight:(float)atof(argv4[1])];
         [samplesList addSample:newSample];
      }
      if (argv2) {  free(argv2); argv2 = NULL; }
      if (argv3) {  free(argv3); argv3 = NULL; }
      if (argv4) {  free(argv4); argv4 = NULL; }
   }
   if (argv) {  free(argv); }
   firstSampleData = [newSample data];
   if (firstSampleData)
   {  hasBoundingBox = [firstSampleData hasBoundingBox];
      isMotionBlurrable = [firstSampleData isMotionBlurrable];
      isCompoundCommand = [firstSampleData isCompoundCommand];
      pushesCTM = [firstSampleData pushesCTM];
      popsCTM = [firstSampleData popsCTM];
   }
   cacheIsFilled = NO;
  
   // restore cmd
   [cmd free];
   cmd = theCmd;
   return self;
}


- produceSampleFromCmd  {  return [eveParser evaluateEveCommand:(char *)[(WWTCLClosedCmd *)cmd cmd]]; }

- (float)lastSampleIsAt {  return [samplesList lastSampleIsAt]; }

- (unsigned long int)maxSampleBandwidth  { return [samplesList maxSampleBandwidth]; }

- resample
{  
   id <WWSample>  newSample;
   id             renderableCommand;
   float          timestamp;


   if (isTCLCommand)
   {  [eveParser evaluateEveCommand:(char *)[cmd cmd]];
      newSample = [[[WWSample alloc] init] setData:[[[TCLCommand alloc] init] 
                                           setCmd:[cmd cmd]] 
                                           timestamp:[sceneClock timestamp] 
                                           generator:[eveParser currentSampleGeneratorName] 
                                           weight:[eveParser currentSampleWeight]];
      [samplesList addSample:newSample];
   }
   else 
   {  // the old one is already in the list...
      renderableCommand = [self produceSampleFromCmd];
      if (!renderableCommand)
      {  NXLogError("warning: the %s <%s> wasn't able to generate a valid renderable object for itself when resampling\n", 
		    [self name], [cmd cmd]);
         return nil;
      }
      newSample = [[[WWSample alloc] init] setData:renderableCommand 
                                           timestamp:[sceneClock timestamp] 
                                           generator:[eveParser currentSampleGeneratorName] 
                                           weight:[eveParser currentSampleWeight]];
      [samplesList addSample:newSample];
   }
   // for now, we'll invalidate my cache and stick this new data sample into it (so boundingBox works)
   cacheIsFilled = NO;
   firstSampleData = [newSample data];

   // if the new sample is between bbIntervalStart and bbIntervalEnd, invalidate the boundingBox cache
   timestamp = [newSample timestamp];
   if ((timestamp >= bbIntervalStart) && (timestamp <= bbIntervalEnd))
   {  dirtyBoundingBox = YES;
      [myShape setBoundingBoxDirty];
   }

   return self;
}

- setMyShape:shape { myShape = shape;  return self; }

- shape { return myShape; }

- (BOOL)hasBoundingBox { return hasBoundingBox; }

- setBoundingBox:(RtBound *)newBoundingBox
{
  N3D_CopyBound(*newBoundingBox, boundingBox);

  return self;
}


// pre-rendering is used for generating shadows and stuff...
// for right now, we don't do any performance hacks.
- preRenderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  return [self renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
}

- preRenderSelf:(WW3DCamera *)camera 
{
  return [self renderSelf:camera];
}


- renderCompoundCommandMaps:theFirstSampleData :theLastSampleData to:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime usingStream:(NXStream *)ns
{
  int                howMany1 = [theFirstSampleData count];
  int                howMany2 = [theLastSampleData count];
  RtFloat            bogusTimeVector[2];
  int                i;
  id <WWRenderable>  data1, data2;


  if (howMany1 == howMany2)  // it looks like these might be worth trying to motion blur together...
  {  for (i = 0; i < howMany1; i++)
     {  // check to see if the two commands are actually of the same type.  If
	// they are, check to see if they're motion blurrable.  If they are,
        // motion blur them.
        data1 = [theFirstSampleData objectAt:i];  // cache the first data a bit
        data2 = [theLastSampleData objectAt:i];  // cache the last data a bit
        if ([data1 class] == [data2 class])
        {  // okay, they're the same kind of command
           if ([data1 isMotionBlurrable])
           {  if ([data1 isCompoundCommand])  // one last check; do we go recursive?
              {  [self renderCompoundCommandMaps:data1 :data2 to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
              }
              else   // okay, it's atomic so try to motion blur it
              {  // actually, I should ask the first command to
                 // compare itself to the second, and only motion 
                 // blur it if it thinks they're different.
                 // also, I could check at this point if the cmd actually does anything (i.e. a Translate of 0 0 0, etc.)

                 // should actually check the length of the list and construct an appropriate vector, 
                 // but prman 3.4 has the restriction that you can only use two samples, sigh...
                 bogusTimeVector[0] = shutterOpenTime;
                 bogusTimeVector[1] = shutterCloseTime;
                
                 if ([data1 theSameAs:data2]) // no need for motion blur; t'aint changing...
                 {  if (![data1 isMoot])  // only render it if it will actually *do* something...
                    {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
                    }
                 }  
                 else
                 {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
                    [data2 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
                 }
              }
	   }
           else // it's not motion blurrable; just use the first command
           {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
           }
	}
        else // the two commands are different; only render the first representation
        {  {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
	   }
        }
     }
  }
  else  // it's not worth trying, just render the commands in the first sample
  {  for (i = 0; i < howMany1; i++)
     {  [[theFirstSampleData objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
     }
  }

  return self;
}


- renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime usingStream:(NXStream *)ns
{
  RtFloat            bogusTimeVector[2];


  if (cacheIsFilled && (shutterOpenTime == lastShutterOpenTime) && (shutterCloseTime == lastShutterCloseTime))
  {  // use my cached representation!!

     // first: is the renderable command I represent even motion-blurrable?
     // second: are we using prman or qrman?  Don't waste this on qrman, it'll just get mad...
     if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
     {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
        {  // use first cached renderable command to be Renderman compliant
           [(id <WWRenderable>)firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
        }
        else // same shot, but need to do it motion blurred!
        {  // should actually check the length of the list and construct an appropriate vector, 
           // but prman 3.4 has the restriction that you can only use two samples, sigh...
           bogusTimeVector[0] = shutterOpenTime;
           bogusTimeVector[1] = shutterCloseTime;

           if (isCompoundCommand) // it's a compound command; need to walk down the two 
           {  [self renderCompoundCommandMaps:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
           }
           else  // it's an atomic RIBCommand...
           {  // actually, I should ask the first command to
              // compare itself to the second, and only motion 
              // blur it if it thinks they're different.
              // also, I could check at this point if ... what?  I forgot...
              if ([firstSampleData theSameAs:lastSampleData]) // no need for motion blur; t'aint changing...
              {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
              }  
              else
              {    [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
                   [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
              }
           }
        }
     }
     else
     {  // either we're going to the screen or this command isn't even motion-blurrable...
        [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
     }
  }
  else
  {  // okay, we're being asked to render a different (set of) sample(s) of myself
     // we'll refill the cache and then render

     // we now need to find the sample in the samplesList that had a
     // timestamp <= shutterOpenTime, where the next sample past it is <
     // shutterOpenTime, and then we need to find the sample that's
     //  timestamp is <= shutterCloseTime where the next sample past it is >
     //  shutterCloseTime.
      
     // probably the best thing to do is have samples be stored in a subclass of List
     // that caches the last answers to questions and searches itself accordingly...
     // In this case, it's a WWSampleList, which is built to store WWSamples
     firstSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterOpenTime] data];
     lastSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterCloseTime] data];
     if (!firstSampleData)
     {  if (!lastSampleData)
	{  NXLogError("couldn't get any valid samples for <%s> for times %f through %f\n", 
		      [cmd cmd], shutterOpenTime, shutterCloseTime);     
           return nil;
        }
        else
	{  NXLogError("couldn't get a valid sample for <%s> at open time %f - using sample at %f...\n", 
		      [cmd cmd], shutterOpenTime, shutterCloseTime);     
           firstSampleData = lastSampleData;
        }
     }
     else
     {  if (!lastSampleData)
	{  NXLogError("couldn't get a valid sample for <%s> at close time %f - using sample at %f...\n", 
		      [(WWTCLClosedCmd *)cmd cmd], shutterCloseTime, shutterOpenTime);     
           lastSampleData = firstSampleData;
        }
     }
     lastShutterOpenTime = shutterOpenTime; 
     lastShutterCloseTime = shutterCloseTime; 
     isMotionBlurrable = [firstSampleData isMotionBlurrable];
     isCompoundCommand = [firstSampleData isCompoundCommand];
     cacheIsFilled = YES;    

     if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
     {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
        {  // use first cached renderable command to be Renderman compliant
           [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
        }
        else // same shot, but need to do it motion blurred!
        {  if (firstSampleData && lastSampleData)
           {  bogusTimeVector[0] = shutterOpenTime;
              bogusTimeVector[1] = shutterCloseTime;

              if (isCompoundCommand) // it's a compound command; need to walk down the two 
              {  [self renderCompoundCommandMaps:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
              }
              else  // it's an atomic RIBCommand...
              {  // actually, I should ask the first command to
                 // compare itself to the second, and only motion 
                 // blur it if it thinks they're different.
                 // also, I could check at this point if ... what?  I forgot...
                 if ([firstSampleData theSameAs:lastSampleData]) // no need for motion blur; t'aint changing...
                 {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
                 }  
                 else
                 {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
                    [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
                 }
              }
 	   }
           if (!firstSampleData)
	   {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
              if (lastSampleData)
              {  [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
	      }
 	   }
           if (!lastSampleData)
	   {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
              if (firstSampleData)
              {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
	      }
 	   }
        } // closes "else // same shot, but need to do it motion blurred!"
     }
     else
     {  // either we're going to the screen or this command isn't even motion-blurrable...
        [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
     }
  }
  return self;
}

- renderMaps:(WW3DCamera *)camera usingStream:(NXStream *)ns
{
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  return [self renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
}


- renderCompoundCommandMaps:theFirstSampleData :theLastSampleData to:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  int                howMany1 = [theFirstSampleData count];
  int                howMany2 = [theLastSampleData count];
  RtFloat            bogusTimeVector[2];
  int                i;
  id <WWRenderable>  data1, data2;


  if (howMany1 == howMany2)  // it looks like these might be worth trying to motion blur together...
  {  for (i = 0; i < howMany1; i++)
     {  // check to see if the two commands are actually of the same type.  If
	// they are, check to see if they're motion blurrable.  If they are,
        // motion blur them.
        data1 = [theFirstSampleData objectAt:i];  // cache the first data a bit
        data2 = [theLastSampleData objectAt:i];  // cache the last data a bit
        if ([data1 class] == [data2 class])
        {  // okay, they're the same kind of command
           if ([data1 isMotionBlurrable])
           {  if ([data1 isCompoundCommand])  // one last check; do we go recursive?
              {  [self renderCompoundCommandMaps:data1 :data2 to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
              }
              else   // okay, it's atomic so try to motion blur it
              {  // actually, I should ask the first command to
                 // compare itself to the second, and only motion 
                 // blur it if it thinks they're different.
                 // also, I could check at this point if the cmd actually does anything (i.e. a Translate of 0 0 0, etc.)

                 // should actually check the length of the list and construct an appropriate vector, 
                 // but prman 3.4 has the restriction that you can only use two samples, sigh...
                 bogusTimeVector[0] = shutterOpenTime;
                 bogusTimeVector[1] = shutterCloseTime;
                
                 if ([data1 theSameAs:data2]) // no need for motion blur; t'aint changing...
                 {  if (![data1 isMoot])  // only render it if it will actually *do* something...
                    {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                    }
                 }  
                 else
                 {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                    [data2 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                 }
              }
	   }
           else // it's not motion blurrable; just use the first command
           {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
           }
	}
        else // the two commands are different; only render the first representation
        {  {  [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
	   }
        }
     }
  }
  else  // it's not worth trying, just render the commands in the first sample
  {  for (i = 0; i < howMany1; i++)
     {  [[theFirstSampleData objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
     }
  }

  return self;
}


- renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  RtFloat            bogusTimeVector[2];


  if (cacheIsFilled && (shutterOpenTime == lastShutterOpenTime) && (shutterCloseTime == lastShutterCloseTime))
  {  // use my cached representation!!

     // first: is the renderable command I represent even motion-blurrable?
     // second: are we using prman or qrman?  Don't waste this on qrman, it'll just get mad...
     if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
     {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
        {  // use first cached renderable command to be Renderman compliant
           [(id <WWRenderable>)firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
        }
        else // same shot, but need to do it motion blurred!
        {  // should actually check the length of the list and construct an appropriate vector, 
           // but prman 3.4 has the restriction that you can only use two samples, sigh...
           bogusTimeVector[0] = shutterOpenTime;
           bogusTimeVector[1] = shutterCloseTime;

           if (isCompoundCommand) // it's a compound command; need to walk down the two 
           {  [self renderCompoundCommandMaps:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
           }
           else  // it's an atomic RIBCommand...
           {  // actually, I should ask the first command to
              // compare itself to the second, and only motion 
              // blur it if it thinks they're different.
              // also, I could check at this point if ... what?  I forgot...
              if ([firstSampleData theSameAs:lastSampleData]) // no need for motion blur; t'aint changing...
              {  if (![firstSampleData isMoot])  // only render it if it will actually *do* something...
                 {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                 }
              }  
              else
              {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                 [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
              }
           }
        }
     }
     else
     {  // either we're going to the screen or this command isn't even motion-blurrable...
        [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
     }
  }
  else
  {  // okay, we're being asked to render a different (set of) sample(s) of myself
     // we'll refill the cache and then render

     // we now need to find the sample in the samplesList that had a
     // timestamp <= shutterOpenTime, where the next sample past it is <
     // shutterOpenTime, and then we need to find the sample that's
     //  timestamp is <= shutterCloseTime where the next sample past it is >
     //  shutterCloseTime.
      
     // probably the best thing to do is have samples be stored in a subclass of List
     // that caches the last answers to questions and searches itself accordingly...
     // In this case, it's a WWSampleList, which is built to store WWSamples
     firstSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterOpenTime] data];
     lastSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterCloseTime] data];
     if (!firstSampleData)
     {  if (!lastSampleData)
	{  NXLogError("couldn't get any valid samples for <%s> for times %f through %f\n", 
		      [cmd cmd], shutterOpenTime, shutterCloseTime);     
           return nil;
        }
        else
	{  NXLogError("couldn't get a valid sample for <%s> at open time %f - using sample at %f...\n", 
		      [cmd cmd], shutterOpenTime, shutterCloseTime);     
           firstSampleData = lastSampleData;
        }
     }
     else
     {  if (!lastSampleData)
	{  NXLogError("couldn't get a valid sample for <%s> at close time %f - using sample at %f...\n", 
		      [(WWTCLClosedCmd *)cmd cmd], shutterCloseTime, shutterOpenTime);     
           lastSampleData = firstSampleData;
        }
     }
     lastShutterOpenTime = shutterOpenTime; 
     lastShutterCloseTime = shutterCloseTime; 
     isMotionBlurrable = [firstSampleData isMotionBlurrable];
     isCompoundCommand = [firstSampleData isCompoundCommand];
     cacheIsFilled = YES;    

     if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
     {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
        {  // use first cached renderable command to be Renderman compliant
           [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
        }
        else // same shot, but need to do it motion blurred!
        {  if (firstSampleData && lastSampleData)
           {  bogusTimeVector[0] = shutterOpenTime;
              bogusTimeVector[1] = shutterCloseTime;

              if (isCompoundCommand) // it's a compound command; need to walk down the two 
              {  [self renderCompoundCommandMaps:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
              }
              else  // it's an atomic RIBCommand...
              {  // actually, I should ask the first command to
                 // compare itself to the second, and only motion 
                 // blur it if it thinks they're different.
                 // also, I could check at this point if ... what?  I forgot...
                 if ([firstSampleData theSameAs:lastSampleData]) // no need for motion blur; t'aint changing...
                 {  if (![firstSampleData isMoot])  // only render it if it will actually *do* something...
                    {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                    }
                 }  
                 else
                 {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                    [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                 }
              }
 	   }
           if (!firstSampleData)
	   {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
              if (lastSampleData)
              {  [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
	      }
 	   }
           if (!lastSampleData)
	   {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
              if (firstSampleData)
              {  [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
	      }
 	   }
        } // closes "else // same shot, but need to do it motion blurred!"
     }
     else
     {  // either we're going to the screen or this command isn't even motion-blurrable...
        [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
     }
  }
  return self;
}

- renderMaps:(WW3DCamera *)camera
{
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  return [self renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
}


- renderCompoundCommand:theFirstSampleData :theLastSampleData to:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  int                howMany1 = [theFirstSampleData count];
  int                howMany2 = [theLastSampleData count];
  RtFloat            bogusTimeVector[2];
  int                i;
  id <WWRenderable>  data1, data2;


  if (howMany1 == howMany2)  // it looks like these might be worth trying to motion blur together...
  {  for (i = 0; i < howMany1; i++)
     {  // check to see if the two commands are actually of the same type.  If
	// they are, check to see if they're motion blurrable.  If they are,
        // motion blur them.
        data1 = [theFirstSampleData objectAt:i];  // cache the first data a bit
        data2 = [theLastSampleData objectAt:i];  // cache the last data a bit
        if ([data1 class] == [data2 class])
        {  // okay, they're the same kind of command
           if ([data1 isMotionBlurrable])
           {  if ([data1 isCompoundCommand])  // one last check; do we go recursive?
              {  [self renderCompoundCommand:data1 :data2 to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
              }
              else   // okay, it's atomic so try to motion blur it
              {  // actually, I should ask the first command to
                 // compare itself to the second, and only motion 
                 // blur it if it thinks they're different.
                 // also, I could check at this point if the cmd actually does anything (i.e. a Translate of 0 0 0, etc.)

                 // should actually check the length of the list and construct an appropriate vector, 
                 // but prman 3.4 has the restriction that you can only use two samples, sigh...
                 bogusTimeVector[0] = shutterOpenTime;
                 bogusTimeVector[1] = shutterCloseTime;
                
                 if ([data1 theSameAs:data2] || (![data1 similarTo:data2])) // no need for motion blur; t'aint changing or is too weird...
                 {  if (![data1 isMoot])  // only render it if it will actually *do* something...
                    {  [data1 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                    }
                 }  
                 else
                 {  RiMotionBeginV((RtInt)2, bogusTimeVector);
                      [data1 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                      [data2 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                    RiMotionEnd();
                 }
              }
	   }
           else // it's not motion blurrable; just use the first command
           {  if (![data1 isMoot])  // only render it if it will actually *do* something...
              {  [data1 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
	      }
           }
	}
        else // the two commands are different; only render the first representation
        {  if (![data1 isMoot])  // only render it if it will actually *do* something...
           {  [data1 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
	   }
        }
     }
  }
  else  // it's not worth trying, just render the commands in the first sample
  {  for (i = 0; i < howMany1; i++)
     {  [[theFirstSampleData objectAt:i] renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
     }
  }

  return self;
}


- renderSelfAsBox:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  RtFloat            bogusTimeVector[2];


  if (cacheIsFilled && (shutterOpenTime == lastShutterOpenTime) && (shutterCloseTime == lastShutterCloseTime))
  {  // use my cached representation!!

     // first: is the renderable command I represent even motion-blurrable?
     // second: are we using prman or qrman?  Don't waste this on qrman, it'll just get mad...
     if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
     {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
        {  // use first cached renderable command to be Renderman compliant
           [(id <WWRenderable>)firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
        }
        else // same shot, but need to do it motion blurred!
        {  // should actually check the length of the list and construct an appropriate vector, 
           // but prman 3.4 has the restriction that you can only use two samples, sigh...
           bogusTimeVector[0] = shutterOpenTime;
           bogusTimeVector[1] = shutterCloseTime;

           if (isCompoundCommand) // it's a compound command; need to walk down the two 
           {  [self renderCompoundCommand:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
           }
           else  // it's an atomic RIBCommand...
           {  // actually, I should ask the first command to
              // compare itself to the second, and only motion 
              // blur it if it thinks they're different.
              // also, I could check at this point if ... what?  I forgot...
              if ([firstSampleData theSameAs:lastSampleData] || (![firstSampleData similarTo:lastSampleData]))
              {  // no need for motion blur; t'aint changing or is too weird...
                 if (![firstSampleData isMoot])  // only render it if it will actually *do* something...
                 {  [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                 }
              }  
              else
              {  RiMotionBeginV((RtInt)2, bogusTimeVector);
                   [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                   [lastSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                 RiMotionEnd();
              }
           }
        }
     }
     else
     {  // either we're going to the screen or this command isn't even motion-blurrable...
        [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
     }
  }
  else
  {  // okay, we're being asked to render a different (set of) sample(s) of myself
     // we'll refill the cache and then render

     // we now need to find the sample in the samplesList that had a
     // timestamp <= shutterOpenTime, where the next sample past it is <
     // shutterOpenTime, and then we need to find the sample that's
     //  timestamp is <= shutterCloseTime where the next sample past it is >
     //  shutterCloseTime.
      
     // probably the best thing to do is have samples be stored in a subclass of List
     // that caches the last answers to questions and searches itself accordingly...
     // In this case, it's a WWSampleList, which is built to store WWSamples
     firstSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterOpenTime] data];
     lastSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterCloseTime] data];
     if (!firstSampleData)
     {  if (!lastSampleData)
	{  NXLogError("couldn't get any valid samples for <%s> for times %f through %f\n", 
		      [cmd cmd], shutterOpenTime, shutterCloseTime);     
           return nil;
        }
        else
	{  NXLogError("couldn't get a valid sample for <%s> at open time %f - using sample at %f...\n", 
		      [cmd cmd], shutterOpenTime, shutterCloseTime);     
           firstSampleData = lastSampleData;
        }
     }
     else
     {  if (!lastSampleData)
	{  NXLogError("couldn't get a valid sample for <%s> at close time %f - using sample at %f...\n", 
		      [(WWTCLClosedCmd *)cmd cmd], shutterCloseTime, shutterOpenTime);     
           lastSampleData = firstSampleData;
        }
     }
     lastShutterOpenTime = shutterOpenTime; 
     lastShutterCloseTime = shutterCloseTime; 
     isMotionBlurrable = [firstSampleData isMotionBlurrable];
     isCompoundCommand = [firstSampleData isCompoundCommand];
     cacheIsFilled = YES;    

     if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
     {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
        {  // use first cached renderable command to be Renderman compliant
           [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
        }
        else // same shot, but need to do it motion blurred!
        {  if (firstSampleData && lastSampleData)
           {  bogusTimeVector[0] = shutterOpenTime;
              bogusTimeVector[1] = shutterCloseTime;

              if (isCompoundCommand) // it's a compound command; need to walk down the two 
              {  [self renderCompoundCommand:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
              }
              else  // it's an atomic RIBCommand...
              {  // actually, I should ask the first command to
                 // compare itself to the second, and only motion 
                 // blur it if it thinks they're different.
                 // also, I could check at this point if ... what?  I forgot...
                 if ([firstSampleData theSameAs:lastSampleData] || (![firstSampleData similarTo:lastSampleData]))
                 {  // no need for motion blur; t'aint changing or is too weird...
                    if (![firstSampleData isMoot])  // only render it if it will actually *do* something...
                    {  [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                    }
                 }  
                 else
                 {  RiMotionBeginV((RtInt)2, bogusTimeVector);
                      [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                      [lastSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                    RiMotionEnd();
                 }
              }
 	   }
           if (!firstSampleData)
	   {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
              if (lastSampleData)
              {  [lastSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
	      }
 	   }
           if (!lastSampleData)
	   {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
              if (firstSampleData)
              {  [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
	      }
 	   }
        } // closes "else // same shot, but need to do it motion blurred!"
     }
     else
     {  // either we're going to the screen or this command isn't even motion-blurrable...
        [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
     }
  }
  return self;
}

- renderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  RtFloat            bogusTimeVector[2];


  if (cacheIsFilled && (shutterOpenTime == lastShutterOpenTime) && (shutterCloseTime == lastShutterCloseTime))
  {  // use my cached representation!!

     // first: is the renderable command I represent even motion-blurrable?
     // second: are we using prman or qrman?  Don't waste this on qrman, it'll just get mad...
     if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
     {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
        {  // use first cached renderable command to be Renderman compliant
           if (![firstSampleData isMoot])  // only render it if it will actually *do* something...
	   {  [(id <WWRenderable>)firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
           }
        }
        else // same shot, but need to do it motion blurred!
        {  // should actually check the length of the list and construct an appropriate vector, 
           // but prman 3.4 has the restriction that you can only use two samples, sigh...
           bogusTimeVector[0] = shutterOpenTime;
           bogusTimeVector[1] = shutterCloseTime;

           if (isCompoundCommand) // it's a compound command; need to walk down the two 
           {  [self renderCompoundCommand:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
           }
           else  // it's an atomic RIBCommand...
           {  // actually, I should ask the first command to
              // compare itself to the second, and only motion 
              // blur it if it thinks they're different.
              // also, I could check at this point if ... what?  I forgot...
              if ([firstSampleData theSameAs:lastSampleData] || (![firstSampleData similarTo:lastSampleData]))
              {  // no need for motion blur; t'aint changing or is too weird...
                 if (![firstSampleData isMoot])  // only render it if it will actually *do* something...
                 {  [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                 }
              }  
              else
              {  RiMotionBeginV((RtInt)2, bogusTimeVector);
                   [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                   [lastSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                 RiMotionEnd();
              }
           }
        }
     }
     else
     {  // either we're going to the screen or this command isn't even motion-blurrable...
        [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
     }
  }
  else
  {  // okay, we're being asked to render a different (set of) sample(s) of myself
     // we'll refill the cache and then render

     // we now need to find the sample in the samplesList that had a
     // timestamp <= shutterOpenTime, where the next sample past it is <
     // shutterOpenTime, and then we need to find the sample that's
     //  timestamp is <= shutterCloseTime where the next sample past it is >
     //  shutterCloseTime.
      
     // probably the best thing to do is have samples be stored in a subclass of List
     // that caches the last answers to questions and searches itself accordingly...
     // In this case, it's a WWSampleList, which is built to store WWSamples
     firstSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterOpenTime] data];
     lastSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterCloseTime] data];
     if (!firstSampleData)
     {  if (!lastSampleData)
	{  NXLogError("couldn't get any valid samples for <%s> for times %f through %f\n", 
		      [cmd cmd], shutterOpenTime, shutterCloseTime);     
           return nil;
        }
        else
	{  NXLogError("couldn't get a valid sample for <%s> at open time %f - using sample at %f...\n", 
		      [cmd cmd], shutterOpenTime, shutterCloseTime);     
           firstSampleData = lastSampleData;
        }
     }
     else
     {  if (!lastSampleData)
	{  NXLogError("couldn't get a valid sample for <%s> at close time %f - using sample at %f...\n", 
		      [(WWTCLClosedCmd *)cmd cmd], shutterCloseTime, shutterOpenTime);     
           lastSampleData = firstSampleData;
        }
     }
     lastShutterOpenTime = shutterOpenTime; 
     lastShutterCloseTime = shutterCloseTime; 
     isMotionBlurrable = [firstSampleData isMotionBlurrable];
     isCompoundCommand = [firstSampleData isCompoundCommand];
     cacheIsFilled = YES;    

     if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING))
     {  if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot  
        {  // use first cached renderable command to be Renderman compliant
           [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
        }
        else // same shot, but need to do it motion blurred!
        {  if (firstSampleData && lastSampleData)
           {  bogusTimeVector[0] = shutterOpenTime;
              bogusTimeVector[1] = shutterCloseTime;

              if (isCompoundCommand) // it's a compound command; need to walk down the two 
              {  [self renderCompoundCommand:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
              }
              else  // it's an atomic RIBCommand...
              {  // actually, I should ask the first command to
                 // compare itself to the second, and only motion 
                 // blur it if it thinks they're different.
                 // also, I could check at this point if ... what?  I forgot...
                 if ([firstSampleData theSameAs:lastSampleData] || (![firstSampleData similarTo:lastSampleData]))
                 {  // no need for motion blur; t'aint changing or is too weird...
                    if (![firstSampleData isMoot])  // only render it if it will actually *do* something...
                    {  [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                    }
                 }  
                 else
                 {  RiMotionBeginV((RtInt)2, bogusTimeVector);
                      [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                      [lastSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
                    RiMotionEnd();
                 }
              }
 	   }
           if (!firstSampleData)
	   {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
              if (lastSampleData)
              {  [lastSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
	      }
 	   }
           if (!lastSampleData)
	   {  NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]);
              if (firstSampleData)
              {  [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
	      }
 	   }
        } // closes "else // same shot, but need to do it motion blurred!"
     }
     else
     {  // either we're going to the screen or this command isn't even motion-blurrable...
        [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
     }
  }
  return self;
}

- renderSelf:(WW3DCamera *)camera
{
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  return [self renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
}


- transformCTM:(WW3DAttributeState *)attributeState startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{ 
  [(id <WWRenderable>)firstSampleData transformCTM:attributeState startingAt:shutterOpenTime endingAt:shutterCloseTime];
  return self; 
}


- calculateBoundingBoxStartingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime  
{
  if ([self hasBoundingBox])
  {  // oy.  This is actually more complex than I'm letting on here.  
     // what I should do is get the sample at the start of the interval, 
     // get the sample at the end of the interval, and then take the max
     // of their two bounding boxes.
     // for now, fake it
     if (firstSampleData)
     {  [self setBoundingBox:[firstSampleData boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime]];
     }
  }

  //dirtyBoundingBox = FALSE; 
  // for now, we won't cache stuff; this will force it to ask the right RIBCommand directly..  

  return self;
}

- (RtBound *)boundingBoxStartingAt:(RtFloat)intervalStart endingAt:(RtFloat)intervalEnd
{ 
   if (!dirtyBoundingBox)
   {  // okay, this means we haven't been sent any messages that would have invalidated the boundingBox cache
      // now check intervals...
      if ((intervalStart == bbIntervalStart) && (intervalEnd == bbIntervalEnd))
      {  // cool! we can use the cached version!!
         return &boundingBox; 
      }
   }
   // damn! have to recalculate it..
   [self calculateBoundingBoxStartingAt:intervalStart endingAt:intervalEnd];
   // don't forget to refill the cache
   bbIntervalStart = intervalStart;
   bbIntervalEnd = intervalEnd;
   dirtyBoundingBox = FALSE;

   // so what things might invalidate the cache?  Basically if I get a
   // new sample that lies with the sample interval, the cache is
   // invalidated.

   return &boundingBox; 
}

- (BOOL)isMotionBlurrable { return isMotionBlurrable; }
- (BOOL)isCompoundCommand { return isCompoundCommand; }

// WavesWorld archiving:
// writeEve:(NXStream *)stream
// writeScene:(NXStream *)stream

- writeEve:(NXStream *)stream atTabLevel:(int)tab
{
   int  i;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "animatable: {%s};", [(WWTCLClosedCmd *)cmd cmd]); 
   return self;
}

- writeScene:(NXStream *)stream atTabLevel:(int)tab
{
   int  i, j, howMany = [samplesList count];
   id   aSample;
   const char *aSampleGeneratorName;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "animatable: {%s} {", [(WWTCLClosedCmd *)cmd cmd]); 
   NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
   for (j = 0; j < howMany; j++)
   {  aSample = [samplesList objectAt:j];
      // let's not bother if this was auto-generated...
      // need a better way of checking this, dude.
      // note that an auto-generated sample has the wrong eveCode (why is that? just think about it...)...
      aSampleGeneratorName = [aSample generatorName];
      // if the name is NULL, write it out, or if the name isn't "WWSampleList" write it out
      if (!aSampleGeneratorName || strcmp("WWSampleList", aSampleGeneratorName))
      {  for (i = 0; i < tab; i++)
         {  NXPrintf(stream, "\t");
         }
         for (i = 0; i < strlen("animatable:  "); i++)
         {  NXPrintf(stream, " ");
         }
         // okay, so how do we want this to look?
         // we have a timestamp for each sample, great
         // now, recall that a sample might be a single WWSample or 
         // it could be a WWSampleComponentList
         // {$timestamp { {$owner1 {$compiledSample1}} }
         // {$timestamp { {$owner1 {$compiledSample1}} {$owner2 {$compiledSample1}} {$owner3 {$compiledSample3}} } } 
   
         NXPrintf(stream, "{%f { ", [aSample timestamp]);
         //[aSample writeEve:stream atTabLevel:0];  // kinda gross, but we've already moved the cursor where we want...
         [aSample writeEve:stream atTabLevel:tab];
         NXPrintf(stream, "}} ");
         NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
      }
   }
   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   for (i = 0; i < strlen("animatable: "); i++)
   {  NXPrintf(stream, " ");
   }
   NXPrintf(stream, "};"); 
   return self;
}

- write3DTextScene:(NXStream *)stream atTabLevel:(int)tab index:(int)index time:(float)time until:(float)lastTime
{
   int  i, j, howMany = [samplesList count];
   id   aSample;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }

   NXPrintf(stream, "startShape (%s); ", [[[samplesList objectAt:0] class] name]);
   // need tab
   // need index (position in current list)
   NXPrintf(stream, 
	    "animatable: {Translate [expr { %d * $__text__(tabLength)}] [expr {$__text__(spacingFactor) * %d * $__text__(spacing) * $__text__(fontSize)}] 0 };\n", 
            tab, index); 
   NXPrintf(stream, "  animatable: {WW3DText $__text__(fontName) $__text__(fontSize) {");

     NXPrintf(stream, "{%s}", [(WWTCLClosedCmd *)cmd cmd]); 
     NXPrintf(stream, "} left;}\n");

     // okay, now put out my instances, pushed back in space
     // the above RIBCommands will have put the cursor in the right place, we now need to shove these guys back in Z, though

     for (j = 0; j < howMany; j++)
     {  aSample = [samplesList objectAt:j];
      // okay, so how do we want this to look?
      // we have a timestamp for each sample, great
      // now, recall that a sample might be a single WWSample or 
      // it could be a WWSampleComponentList
      // {$owner1 {$compiledSample1}
      // {$owner1 {$compiledSample1}} {$owner2 {$compiledSample1}} {$owner3 {$compiledSample3}}

      NXPrintf(stream, "    ");
      // if there are more samples in the list, we hand in the next's timestamp as the "last" time
      // otherwise we hand in this sample's timestamp again, so it knows it's the last
      if (j == (howMany - 1))
      {  [aSample write3DTextScene:stream atTabLevel:0 index:0 time:[aSample timestamp] until:[aSample timestamp]]; // we've already move the origin in X and Y
      }
      else
      {  [aSample write3DTextScene:stream atTabLevel:0 index:0 time:[aSample timestamp] until:[[samplesList objectAt:(j+1)] timestamp]]; // we've already move the origin in X and Y
      }

      NXPrintf(stream, "\n");
   }

   NXPrintf(stream, "endShape;\n");
   return self;
}

- writeInventorAtTime:(float)currentTime to:(NXStream *)stream atTabLevel:(int)tab
{
   int                i;
   id <WWRenderable>  data;


   data = [(WWSample *)[samplesList sampleAtOrJustBefore:currentTime] data];
   if ([data isMoot])
   {  return self;
   }

   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "# animatable: {%s}\n", [(WWTCLClosedCmd *)cmd cmd]); 
   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "# at time %f, this animatable expression evaluated to:\n", currentTime);
   [data writeInventorAtTime:currentTime to:stream atTabLevel:tab];
   return self;
}


// NeXTSTEP archiving:

#define typeVector "@@@ccc@@@c"
#define typeValues &cmd, &eveParser, &interp, \
                   &hasBoundingBox, &isMotionBlurrable, &isTCLCommand, \
                   &myShape, &samplesList, &sceneClock, \
                   &isCompoundCommand 

- read:(NXTypedStream *)stream 
{
  int version;


  [super read:stream];

  version = NXTypedStreamClassVersion(stream, "EveCommand");
  if (version == 0) NXReadTypes(stream, "i", &version), version=1;
  if (version == 1)
  {  NXReadTypes(stream, typeVector, typeValues);
     NXReadArray(stream, "f", 6, boundingBox);
  } 
  if (version == 2)
  {  NXReadTypes(stream, typeVector, typeValues);
     NXReadArray(stream, "f", 6, boundingBox);
     NXReadTypes(stream, "cc", &pushesCTM, &popsCTM);
  } 

  return self; 
}

- write:(NXTypedStream *)stream 
{
  [super write:stream];

  NXWriteTypes(stream, typeVector, typeValues);
  NXWriteArray(stream, "f", 6, boundingBox);
  NXWriteTypes(stream, "cc", &pushesCTM, &popsCTM);

  return self;
}

- (BOOL)theSameAs:otherRIBCommand
{
  // make the default NO...  
  return NO;
}

- (BOOL)similarTo:otherRIBCommand 
{
  if ([self class] != [otherRIBCommand class])
  {  return NO;
  }
  return YES;
}

- (BOOL)isMoot
{
  return NO;
}

- (BOOL)isMootStartingAt:(float)startTime endingAt:(float)endTime  
{  
   if ([[(WWSample *)[samplesList sampleAtOrJustBefore:startTime] data] isMoot])
   {  return YES;
   }
   if ([[(WWSample *)[samplesList sampleAtOrJustBefore:endTime] data] isMoot])
   {  return YES;
   }
   return NO;
}


- (const char *)sampleName { return sampleName; }

// boy, this is dumb... This is to get around the stupid warnings from the compiler - ask wave for details
- class { return [super class]; }

- command
{
    return cmd;
}


@end





